package instruction

import (
	"errors"
	"fmt"
	"log"

	"gitee.com/microwww/jvm-go/jvm/class"
	"gitee.com/microwww/jvm-go/jvm/native"
	"gitee.com/microwww/jvm-go/jvm/rtda"
	"gitee.com/microwww/jvm-go/jvm/tool"
)

type Instruction interface {
	FetchOperands(buffer *tool.Buffer)
	Execute(frame *rtda.MethodFrame)
}

func Instruct(th *rtda.Thread, method *class.Method, args ...*class.Object) (*rtda.MethodFrame, error) {
	// th = rtda.NewThread()
	frame := rtda.NewMethodFrame(method)
	th.PushMethodFrame(frame)
	method.Class().ClassLoader()
	for i, arg := range args {
		frame.LocalVar().SetRef(uint(i), arg)
	}
	return frame, running(frame, method)
}

func RunThread(th *rtda.Thread) (*rtda.MethodFrame, error) {
	frame := th.CurrentMethodFrame()
	return frame, RunMethodFrame(frame)
}

func RunMethodFrame(frame *rtda.MethodFrame) error {
	return running(frame, frame.Method())
}

func running(frame *rtda.MethodFrame, method *class.Method) (er error) {
	if method.IsNative() {
		return runNative(frame, method)
	}
	bf := tool.NewBuffer([]byte{})

	defer func() {
		e := recover()
		if e != nil {
			log.Default().Printf("Run method Error ! `%v` method %v.%v%v : %v \n",
				method.Modifier(), method.Class().Name(), method.Name(), method.Descriptor(), e)
			printFrame(frame)
			er = errors.New(fmt.Sprintf("Error: %v", e))
		}
	}()

	log.Default().Printf("Run method %v.%v %v", method.Class().Name(), method.Name(), method.Descriptor())
	thread := frame.Thread()
	for {
		bf.Reset(method.Code())
		pc := frame.NextPC()
		frame.Thread().SetPC(pc)
		op, _ := bf.Skip(pc).ReadByte()

		a := newInstruction(op)
		log.Default().Printf("\t\t %02d \t %T \n", pc, a)
		a.FetchOperands(bf)
		frame.SetNextPC(bf.Offset())
		a.Execute(frame)

		printFrame(frame)

		// return 会 POP frame 证实方法已经结束, 可以跳出
		if thread.CurrentMethodFrame() != frame {
			log.Default().Printf("RETURN method : %v.%v, %v", method.Class().Name(), method.Name(), method.Descriptor())
			return
		}
	}
}

func runNative(frame *rtda.MethodFrame, method *class.Method) error {
	fun, ok := native.GetMethod(method)
	if ok {
		log.Default().Printf("Run native method %v.%v %v", method.Class().Name(), method.Name(), method.Descriptor())
		fun(frame)
	} else {
		// TODO :: do not support
		log.Default().Printf("Run error native method %v.%v %v with DEFAULT zero", method.Class().Name(), method.Name(), method.Descriptor())
		native.None(frame, method)
	}
	return nil
}

func printFrame(frame *rtda.MethodFrame) {
	method := frame.Method()
	log.Default().Printf(
		"Dump method %v.%v %v \n \t\t LocalVar : %v \n \t\t OperationStack : %v",
		method.Class().Name(), method.Name(), method.Descriptor(),
		frame.LocalVar(), *frame.OperationStack())
}
